#include "cmd_parser.h"

FILE * cmd_file;

int fetchCmdLine(char * line, int line_num);
int insertLabel(char * label, int line_num);
int parseCmdToArgs(char * cmd, int cmd_num, char * original) ;

int validRegisters(Command * cmd);
int validImm(Command * cmd);
int validBranch(Command * cmd);
int validDataTransfer(Command * cmd);
int validJump(Command * cmd);
int validHalt(Command * cmd);

int isAddress(char * argument, int * offset, int * regNum);
int isLabel(char * argument, Command * cmd);

char * instructions[] = {"add","sub","mul","div","addi","subi",
						     "lw","sw","beq","bne","slt","slti","j","halt"};


int (*validationPtr[]) (Command * cmd) = 
			{validRegisters,validRegisters,validRegisters,validRegisters,
			validImm,validImm,
			validDataTransfer,validDataTransfer,
			validBranch,validBranch,validRegisters,validImm,
			validJump,
			validHalt};

int loadCmdFile(char * filepath) {

	char cmd_line[MAX_CMD_LINE_LENGTH];
	int line_num = 0;
	cmd_count = 0;

	printf("Loading commands file...\n");

	cmd_file= fopen(filepath, "r");
	if (cmd_file == NULL)
		return -1;

	commands = (Command *)malloc(sizeof(Command));
		if (commands == NULL)
			return -1;

	while(fgets(cmd_line, MAX_CMD_LINE_LENGTH, cmd_file) != NULL) {

		if (trimwhitespace(cmd_line)[0] == '\0')
			continue;

		fetchCmdLine(cmd_line, line_num++);

		
	}

	fclose(cmd_file);

	return 0;
}

int fetchCmdLine(char * line, int line_num) {
	
	Command * tmpPtr;
	char * label;
	char * tmp_line;
	char * original_line;

	//allocate memory for new line
	tmpPtr = (Command *)realloc(commands, sizeof(Command)*(cmd_count + 1));
	if (tmpPtr == NULL)
		return -1;
	commands = tmpPtr;
	
	cmd_count++;

	original_line = (char *)malloc(sizeof(char)*strlen(line)+1);

	strcpy(original_line,line);

	if (strstr(line, ":") != NULL)
	{	
		label = strtok(line, ":");
		if (insertLabel(label, line_num) == -1) return -1;
		
		tmp_line = strtok(NULL, "\t");
	
	}
	else {
		tmp_line = strtok(line, "\t");
	}

	if (parseCmdToArgs(tmp_line, cmd_count - 1, original_line) == -1) return -1;

	free(original_line);

	return 0;
}

int insertLabel(char * label, int line_num) {

	Label * tmpPtr;

	
	//allocate memory for new label
	if (labels == NULL)
	{
		labels = (Label *)malloc(sizeof(Label));
		if (labels == NULL)
			return -1;

		labels_count = 1;

	} else {
		tmpPtr = (Label *)realloc(labels, (labels_count + 1)*sizeof(Label));
		if (tmpPtr == NULL)
			return -1;
		
		labels = tmpPtr;
		labels_count++;

	}

	labels[labels_count-1].address = line_num;
	strcpy(labels[labels_count-1].name, label);

	return 0;
}

void printLabelsArr() {
	int i;
	
	printf("\nLabels detected:\n");

	for (i = 0; i < labels_count; i++) {
		printf("%s\t%d\n", labels[i].name, labels[i].address);
	}
}

int findLabel(char * label) {
	int i;
	for (i = 0 ; i < labels_count ; i++) {
		if (!strcmp(label,labels[i].name)) return labels[i].address;
	}

	return -1;
}

int parseCmdToArgs(char * cmd, int cmd_num, char * original) {
       char *  p;
        int n_delimeters = 0;

        strip_newline(original, strlen(original));

		strcpy(commands[cmd_num].command_str, original);

		strip_newline(cmd, strlen(cmd));

        p = strtok (cmd, " ");
        commands[cmd_num].instruction = (char *)malloc(sizeof(char)*(strlen(p) + 1));
        if (commands[cmd_num].instruction == NULL)
                return -1;

        strcpy(commands[cmd_num].instruction, p);

        p = strtok (NULL, ",");
        /* split string and append tokens to 'res' */
        commands[cmd_num].argv = (char **)malloc (sizeof (char*));
        while (p) {
                commands[cmd_num].argv = (char **)realloc (commands[cmd_num].argv, sizeof (char*) * ++n_delimeters);

          if (commands[cmd_num].argv == NULL)
                return -1; /* memory allocation failed */

          commands[cmd_num].argv[n_delimeters-1] = (char *)malloc(sizeof(char) * (strlen(trimwhitespace(p)) + 1));
          if (commands[cmd_num].argv[n_delimeters-1] == NULL)
                  return -1;

          strcpy(commands[cmd_num].argv[n_delimeters-1], trimwhitespace(p));

          p = strtok (NULL, ",");
        }

        commands[cmd_num].argc = n_delimeters;

		commands[cmd_num].inst_num = findMemberIndex(commands[cmd_num].instruction, instructions, INSRTUCTIONS_TYPES_CNT);

		/* initialization of instruction arguments */
		commands[cmd_num].imm = 0;
		strcpy(commands[cmd_num].label,"");
		commands[cmd_num].offset = 0;
		commands[cmd_num].rd = 0;
		commands[cmd_num].rs = 0;
		commands[cmd_num].rt = 0;

		if (!((validationPtr[commands[cmd_num].inst_num]) (&commands[cmd_num])))
			return -1;

        return 0;
}



int printArgv(int address, FILE * target) {

	int i;

	for (i = 0; i < commands[address].argc; i++) {
		fprintf(target, "%s", commands[address].argv[i]);
		if (i < commands[address].argc-1)
			fprintf(target,",");
	}

	return 0;

}


int validRegisters(Command * cmd) {
	if (cmd->argc != 3 ||
	    (cmd->rd = isRegister(cmd->argv[0])) == -1 ||
	    ((cmd->rs = isRegister(cmd->argv[1])) == -1) ||
	    ((cmd->rt = isRegister(cmd->argv[2])) == -1)) return 0;
	
	return 1;
}

int validImm(Command * cmd) {
	if (cmd->argc != 3 || 
		(cmd->rt = isRegister(cmd->argv[0])) == -1 ||
		(cmd->rs = isRegister(cmd->argv[1])) == -1 ||
		!(isNumeric(cmd->argv[2],&(cmd->imm),1))) return 0;

	return 1;
}

int validBranch(Command * cmd) {
	if (cmd->argc != 3 ||
		(cmd->rs = isRegister(cmd->argv[0])) == -1 ||
		(cmd->rt = isRegister(cmd->argv[1])) == -1 ||
		!isLabel(cmd->argv[2],cmd)) return 0;

	return 1;
}

int validDataTransfer(Command *cmd) {
	if (cmd->argc != 2 ||
		(cmd->rt = isRegister(cmd->argv[0])) == -1 ||
		!isAddress(cmd->argv[1],&(cmd->offset),&(cmd->rs)))
			return 0;

	return 1;
}

int validJump(Command * cmd) { 
	if (cmd->argc != 1 || !isLabel(cmd->argv[0],cmd)) return 0;
	return 1; 
}

int validHalt(Command * cmd) { 
	if (cmd->argc != 0) return 0;
	return 1; 
}

int isAddress(char * argument, int * offset, int * regNum) {
	int i = 1;
	char * offsetStr;

	if (argument[0] != '(') return 0;
	if (argument[1] == '-') i++;
	do {
		if (!isdigit(argument[i]))
			return 0;
		i++;
	}
	while (argument[i] != ')');

	offsetStr = (char *)malloc(i*sizeof(char));
	if (offsetStr == NULL)
			return 0;


	if (offset != NULL && regNum != NULL) {
		sprintf(offsetStr,"%.*s",i-1,argument+1);
		*offset = atoi(offsetStr);
		*regNum = isRegister(argument+i+1);
	}
	
	free(offsetStr);

	if (*regNum == -1) return 0;

	return 1;

}

int isLabel(char * argument, Command * cmd) {
	if (!(isRegister(argument) != -1 || isNumeric(argument,NULL,1) || isAddress(argument,NULL,NULL))) {
		strcpy(cmd->label,argument);
		return 1;
	}
	else return 0;
}

int init_commands() {
	int i, j;
	if (commands != NULL) {
		for (i = 0; i < cmd_count; i++) {
			free(commands[i].instruction);
			for (j = 0; j < commands[i].argc; j++) {
				free(commands[i].argv[j]);
			}
		}

		free(commands);
		cmd_count = 0;
	}
	if (labels != NULL) {
		free(labels);
		labels_count = 0;
	}

	commands = NULL;
	labels = NULL;

	return 0;
}
